package org.wannatrak.android.service;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.util.Log;
import com.google.android.maps.GeoPoint;
import com.google.android.maps.OverlayItem;
import org.apache.http.HttpResponse;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.wannatrak.android.ApplicationContext;
import org.wannatrak.android.R;
import org.wannatrak.android.entity.Credential;
import org.wannatrak.android.entity.Track;
import org.wannatrak.android.entity.TrackStatus;
import org.wannatrak.android.enums.HttpRequestMethod;
import org.wannatrak.android.exceptions.HttpException;
import org.wannatrak.android.formatter.DateTimeFormatter;
import org.wannatrak.android.helper.JsonHelper;
import org.wannatrak.android.helper.WannatrakHelper;
import org.wannatrak.android.intf.Queries;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.*;

/**
 * @Author: Maksim Tulupov maxtulupov@gmail.com
 * @Date: $Date: 2011-12-05 04:56:47 +0000 (Mon, 05 Dec 2011) $ 4:39 PM
 * @version: $Revision: 34 $
 * @id: $Id: WannatrakService.java 34 2011-12-05 04:56:47Z maxtulupov@gmail.com $
 */
public class WannatrakService {

    private Context context;

    public WannatrakService(Context context) {
        this.context = context;
    }

    public void deleteTrack(String trackId) throws HttpException {
        HttpService httpService = ApplicationContext.getHttpService();
        try {
            HttpResponse response = httpService.sendRequest(ApplicationContext.URL +
                    "/device/api/trak/remove/" + ApplicationContext.getSettingService().loadSetting("deviceKey")
                    + "/" + trackId, null, null, HttpRequestMethod.DELETE);
            if (!httpService.isResponseOk(response)) {
                Log.e(getClass().getSimpleName() + " deleteTrack", "server return error " + response.getStatusLine()
                        + " device key = " + ApplicationContext.getSettingService().loadSetting("deviceKey"));
                throw new HttpException(context.getString(R.string.err_server_return_error));
            }
        } catch (Exception e) {
            Log.e(getClass().getSimpleName(), "error send request", e);
            if (e instanceof HttpException) {
                throw (HttpException) e;
            }
            throw new HttpException(context.getString(R.string.err_server_access));
        }
    }

    public boolean login() throws HttpException {
        String login = ApplicationContext.getSettingService().loadSetting("login");
        String password = ApplicationContext.getSettingService().loadSetting("password");
        return login(login, password);
    }

    public boolean login(String login, String password) throws HttpException {
        JSONObject loginObject = createLoginObject(login, password);
        HttpService httpService = ApplicationContext.getHttpService();
        try {
            HttpResponse response = httpService.sendRequest(ApplicationContext.URL + "/device/api/login", loginObject);
            if (!httpService.isResponseOk(response)) {
                Log.e(getClass().getSimpleName(), "server return error " + response.getStatusLine());
                throw new HttpException(context.getString(R.string.err_server_return_error));
            }
            String s = httpService.readAsString(response).trim();
            ContentValues cv = new ContentValues();
            cv.put(Credential.DEVICE_KEY, s);
            if (isCredentialExist(login)) {
                context.getContentResolver().update(Credential.CONTENT_URI, cv, Credential.LOGIN + " = ?",
                        new String[]{login});
            } else {
                cv.put(Credential.LOGIN, login);
                cv.put(Credential.PASSWORD, password);
                context.getContentResolver().insert(Credential.CONTENT_URI, cv);
            }
            ApplicationContext.getSettingService().saveSetting("login", login);
            ApplicationContext.getSettingService().saveSetting("password", password);
            ApplicationContext.getSettingService().saveSetting("deviceKey", s);
            ApplicationContext.getSettingService().saveSetting("currentTrack", login);
            return true;
        } catch (IOException e) {
            Log.e(getClass().getSimpleName(), "error", e);
            throw new HttpException(context.getString(R.string.err_server_access));
        }
    }

    public void logout() throws HttpException {
        HttpService httpService = ApplicationContext.getHttpService();
        try {
            HttpResponse response = httpService.sendRequest(ApplicationContext.URL +
                    "/device/api/logout", ApplicationContext.getSettingService().loadSetting("deviceKey").getBytes());
            if (!httpService.isResponseOk(response)) {
                Log.e(getClass().getSimpleName() + " logout", "server return error " + response.getStatusLine()
                        + " device key = " + ApplicationContext.getSettingService().loadSetting("deviceKey"));
                throw new HttpException(context.getString(R.string.err_server_return_error));
            }
        } catch (Exception e) {
            Log.e(getClass().getSimpleName(), "error send request", e);
            if (e instanceof HttpException) {
                throw (HttpException) e;
            }
            throw new HttpException(context.getString(R.string.err_server_access));
        }
    }

    public String createTrack(String name, String interval, String saveInterval) throws HttpException {
        HttpService httpService = ApplicationContext.getHttpService();
        try {
            HttpResponse response = httpService.sendRequest(ApplicationContext.URL +
                    "/device/api/trak/create/" + ApplicationContext.getSettingService().loadSetting("deviceKey")
                    + "/" + encode(name), null, Collections.singletonMap("Accept", "text/json"), HttpRequestMethod.GET);
            if (!httpService.isResponseOk(response)) {
                Log.e(getClass().getSimpleName() + " createTrack", "server return error " + response.getStatusLine()
                        + " device key = " + ApplicationContext.getSettingService().loadSetting("deviceKey"));
                throw new HttpException(context.getString(R.string.err_server_return_error));
            }
            JSONObject jsonObject = httpService.readAsJson(response);
            String trackId = jsonObject.getString("id");
            ApplicationContext.getSettingService().saveSetting("trackId", trackId);
            continueTrack();
            updateTrack(name, interval, saveInterval);
            return trackId;
        } catch (Exception e) {
            Log.e(getClass().getSimpleName(), "error send request", e);
            if (e instanceof HttpException) {
                throw (HttpException) e;
            }
            throw new HttpException(context.getString(R.string.err_server_access));
        }
    }

    public JSONArray loadTracks() throws HttpException {
        try {
            HttpResponse response = ApplicationContext.getHttpService().sendRequest(ApplicationContext.URL + "/device/api/trak/list/"
                    + ApplicationContext.getSettingService().loadSetting("deviceKey"), null, HttpRequestMethod.GET);
            if (!ApplicationContext.getHttpService().isResponseOk(response)) {
                Log.e(getClass().getSimpleName() + " loadTracks", "response contains error " + response.getStatusLine()
                        + " device key = " + ApplicationContext.getSettingService().loadSetting("deviceKey"));
                throw new HttpException(context.getString(R.string.err_server_return_error));
            }

            String content = ApplicationContext.getHttpService().readAsString(response);
            if (!content.startsWith("{")) {
                content = "{array:" + content + "}";
            }
            JSONObject object = ApplicationContext.getHttpService().readAsJson(content);
            JSONArray array = object.getJSONArray("array");
            return array;

        } catch (Exception e) {
            Log.e(getClass().getSimpleName(), "error send request", e);
            if (e instanceof HttpException) {
                throw (HttpException) e;
            }
            throw new HttpException(context.getString(R.string.err_server_access));
        }
    }

    public List<OverlayItem> loadItems(String trackId, String trackName, long startDate, long endDate) throws HttpException {
        try {
            HttpResponse response = ApplicationContext.getHttpService().sendRequest(ApplicationContext.URL + "/device/api/trak/"
                    + ApplicationContext.getSettingService().loadSetting("deviceKey")
                    + "/" + trackId + "/" + startDate + "/" + endDate + "/false", null, Collections.singletonMap("Accept", "text/json"), HttpRequestMethod.GET);
            if (!ApplicationContext.getHttpService().isResponseOk(response)) {
                Log.e(getClass().getSimpleName() + " loadItems", "response contains error " + response.getStatusLine()
                        + " device key = " + ApplicationContext.getSettingService().loadSetting("deviceKey"));
                throw new HttpException(context.getString(R.string.err_server_return_error));
            }
            String s = ApplicationContext.getHttpService().readAsString(response);
            if (!s.startsWith("{")) {
                s = "{array:" + s + "}";
            }
            JSONObject result = new JSONObject(s);
            JSONArray jsonArray = result.getJSONArray("array");
            List<OverlayItem> list = new LinkedList<OverlayItem>();
            JSONArray arr = null;
            for (int i = 0; i < jsonArray.length(); i++) {
                try {
                    arr = (JSONArray) jsonArray.get(i);
                    GeoPoint gp = new GeoPoint((int) (arr.getDouble(2) * ApplicationContext.LOC_MULTIPLY),
                            (int) (arr.getDouble(1) * ApplicationContext.LOC_MULTIPLY));
                    Date date = new Date(arr.getLong(0));
                    String dateStr = ApplicationContext.getFormatter(DateTimeFormatter.class).format(date);
                    String speedStr = arr.getString(3);
                    if (speedStr == null) {
                        speedStr = "0.0";
                    }
                    String altitudeStr = arr.getString(5);
                    if (altitudeStr == null) {
                        altitudeStr = "0.0";
                    }
                    StringBuilder sb = new StringBuilder(trackName).append(" ").append(dateStr).append("\n")
                            .append(context.getString(R.string.details)).append(" ")
                            .append(String.valueOf(speedStr)).append(" ").append(context.getString(R.string.kmh)).append("\n")
                            .append(String.valueOf(altitudeStr)).append(" ").append(context.getString(R.string.masl));
                    OverlayItem oi = new OverlayItem(gp, "", sb.toString());
                    list.add(oi);
                } catch (JSONException e) {
                    Log.e(getClass().getSimpleName(), "error convert into coord " + arr, e);
                }
            }
            return list;
        } catch (Exception e) {
            Log.e(getClass().getSimpleName(), "error send request", e);
            if (e instanceof HttpException) {
                throw (HttpException) e;
            }
            throw new HttpException(context.getString(R.string.err_server_access));
        }
    }

    public void sendTracks() throws HttpException {
        Log.d(getClass().getSimpleName(), "start send tracks");
        deleteSendedTracks();
        String tracks = prepareTracks();
        if (tracks != null) {
            Log.e(getClass().getSimpleName(), "location created " + tracks);
            try {
                HttpService httpService = ApplicationContext.getHttpService();
                Log.d(getClass().getSimpleName(), "try to send request");
                HttpResponse response = httpService.sendRequest(
                        ApplicationContext.URL + "/device/api/trak/" + ApplicationContext.getSettingService().loadSetting("deviceKey"), tracks.getBytes());
                if (!httpService.isResponseOk(response)) {
                    if (response.getStatusLine().getStatusCode() == 409) {
                        Log.e(getClass().getSimpleName(), " error, send tracks before sendPeriod Elapsed");
                    } else {
                        Log.e(getClass().getSimpleName() + " sendTracks", "response contains error " + response.getStatusLine().toString()
                                + " device key = " + ApplicationContext.getSettingService().loadSetting("deviceKey"));
                        throw new HttpException("response contains error " + response.getStatusLine().toString());
                    }

                }
                Log.d(getClass().getSimpleName(), "tracks sended");
                updateTrackStatus();
            } catch (IOException e) {
                Log.e(getClass().getSimpleName(), "error send to server", e);
                throw new HttpException("error send to server", e);
            }
        } else {
            Log.d(getClass().getSimpleName(), "nothing send");
        }
    }

    public boolean continueTrack(String name, String interval, String saveInterval, String trackId) throws HttpException {
        try {
            ApplicationContext.getSettingService().saveSetting("trackId", trackId);
            continueTrack();
            updateTrack(name, interval, saveInterval);
        } catch (Exception e) {
            Log.e(getClass().getSimpleName(), "error send request", e);
            if (e instanceof HttpException) {
                throw (HttpException) e;
            }
            throw new HttpException(context.getString(R.string.err_server_access));
        }
        return true;
    }

    public void continueTrack() throws HttpException {
        HttpService httpService = ApplicationContext.getHttpService();
        try {
            HttpResponse response = httpService.sendRequest(ApplicationContext.URL + "/device/api/trak/continue/"
                    + ApplicationContext.getSettingService().loadSetting("deviceKey") + "/"
                    + ApplicationContext.getSettingService().loadSetting("trackId"),
                    null, HttpRequestMethod.GET);
            if (!httpService.isResponseOk(response)) {
                Log.e(getClass().getSimpleName() + " continueTrack", "server return error " + response.getStatusLine()
                        + " device key = " + ApplicationContext.getSettingService().loadSetting("deviceKey"));
                throw new HttpException(context.getString(R.string.err_server_return_error));
            }

            String newDeviceKey = httpService.readAsString(response).trim();
            ApplicationContext.getSettingService().saveSetting("deviceKey", newDeviceKey);
            Log.e(getClass().getSimpleName(), "new device key = " + newDeviceKey);
        } catch (Exception e) {
            Log.e(getClass().getSimpleName(), "error send request", e);
            if (e instanceof HttpException) {
                throw ((HttpException) e);
            }
            throw new HttpException(context.getString(R.string.err_server_access));
        }
    }

    public void updateTrack(String trackName, String trackInterval, String saveInterval) throws HttpException {
        HttpService httpService = ApplicationContext.getHttpService();
        try {
            JSONObject settingObject = createSettingObject(trackName, trackInterval, saveInterval);
            HttpResponse response = httpService.sendRequest(ApplicationContext.URL +
                    "/device/api/trak/settings/" + ApplicationContext.getSettingService().loadSetting("deviceKey"),
                    settingObject);
            if (!httpService.isResponseOk(response)) {
                Log.e(getClass().getSimpleName() + " updateTrack", "server return error " + response.getStatusLine()
                        + " device key = " + ApplicationContext.getSettingService().loadSetting("deviceKey"));
                throw new HttpException(context.getString(R.string.err_server_return_error));
            }
        } catch (Exception e) {
            Log.e(getClass().getSimpleName(), "error send request", e);
            if (e instanceof HttpException) {
                throw ((HttpException) e);
            }
            throw new HttpException(context.getString(R.string.err_server_access));
        }
    }

    private String prepareTracks() {
        Log.d(getClass().getSimpleName(), "start prepare tracks");
        Cursor c = null;
        try {
            c = context.getContentResolver().query(Track.CONTENT_URI, null, Track.STATUS + " = " + TrackStatus.CREATED.getId(),
                    null, null);
            if (c.moveToFirst()) {
                StringBuilder sb = new StringBuilder("[");
                do {
                    sb.append(createLocation(c)).append(",");
                } while (c.moveToNext());
                String s = sb.toString();
                String result = s.substring(0, s.length() - 1);
                Log.e(getClass().getSimpleName(), "tracks prepared " + result + "]");
                return result += "]";
            } else {
                return null;
            }
        } finally {
            if (c != null) {
                c.close();
            }
        }
    }

    private void updateTrackStatus() {
        ContentValues cv = new ContentValues();
        cv.put(Track.STATUS, TrackStatus.SENDED.getId());
        context.getContentResolver().update(Track.CONTENT_URI, cv, Track.STATUS + " = ?",
                new String[]{String.valueOf(TrackStatus.CREATED.getId())});
    }

    private String createLocation(Cursor c) {
        StringBuilder sb = new StringBuilder("[");
        sb.append(c.getLong(c.getColumnIndex(Track.CREATE_DATE))).append(",")
                .append(c.getDouble(c.getColumnIndex(Track.LONGITUDE))).append(",")
                .append(c.getDouble(c.getColumnIndex(Track.LATITUDE))).append(",")
                .append(c.getDouble(c.getColumnIndex(Track.SPEED))).append(",")
                .append(c.getDouble(c.getColumnIndex(Track.BEARING))).append(",")
                .append(c.getDouble(c.getColumnIndex(Track.ALTITUDE))).append("]");
        return sb.toString();
    }

    private void deleteSendedTracks() {
        Log.e(getClass().getSimpleName(), "start delete old tracks");
        int i = context.getContentResolver().delete(Track.CONTENT_URI, Track.STATUS + " = " + TrackStatus.SENDED.getId(), null);
        Log.e(getClass().getSimpleName(), "deleted track count = " + i);
    }

    private JSONObject createLoginObject(String login, String password) {
        Map<String, String> data = new HashMap<String, String>();
        data.put("password", password);
        data.put("login", login);
        return JsonHelper.createJsonObject(data);
    }

    private JSONObject createSettingObject(String trackName, String trackInterval, String saveInterval) {
        Map<String, String> data = new HashMap<String, String>();
        data.put("savePeriod", String.valueOf(WannatrakHelper.convertStringIntervalInSeconds(saveInterval)));
        data.put("sendPeriod", String.valueOf(WannatrakHelper.convertStringIntervalInMinutes(trackInterval)));
        data.put("name", trackName);
        return JsonHelper.createJsonObject(data);
    }

    private boolean isCredentialExist(String login) {
        Cursor c = null;
        try {
            c = ApplicationContext.getDatabaseHelper().executeRawSQL(Queries.IS_CREDENTIAL_EXIST, new String[]{login});
            if (c.moveToFirst()) {
                return c.getInt(c.getColumnIndex(Credential._COUNT)) > 0;
            } else {
                return false;
            }
        } finally {
            if (c != null) {
                c.close();
            }
        }
    }

    private boolean loadCredentials() {
        Cursor c = context.getContentResolver().query(Credential.CONTENT_URI, null, null, null, Credential._ID + " desc");
        try {
            if (c.moveToFirst()) {
                ApplicationContext.getSettingService().saveSetting("login", c.getString(c.getColumnIndex(Credential.LOGIN)));
                ApplicationContext.getSettingService().saveSetting("password", c.getString(c.getColumnIndex(Credential.PASSWORD)));
                ApplicationContext.getSettingService().saveSetting("deviceKey", c.getString(c.getColumnIndex(Credential.DEVICE_KEY)));
                return true;
            } else {
                return false;
            }
        } finally {
            c.close();
        }
    }

    private String encode(String value) {
        try {
            return URLEncoder.encode(value, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }
}
